//go:build !windows

package main

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/creativeprojects/clog"
	"github.com/creativeprojects/resticprofile/config"
	"github.com/creativeprojects/resticprofile/crond"
	"github.com/creativeprojects/resticprofile/term"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const scheduleIntegrationTestsConfiguration = `
version: "2"

global:
  scheduler: crontab:*:%s

groups:
  two-profiles:
    profiles:
      - profile-schedule-inline
      - profile-schedule-struct

profiles:
  default-inline:
    backup:
      schedule-permission: "user"

  default-struct:
    backup:
      schedule:
        permission: "user"

  profile-schedule-inline:
    backup:
      schedule: "*:00,30"
      schedule-permission: "user"

  profile-schedule-struct:
    backup:
      schedule:
        at: "*:20,50"
        permission: "user"

`

const scheduleIntegrationTestsCrontab = `
### this content was generated by resticprofile, please leave this line intact ###
10,40 * * * *	user	cd /workdir && /home/resticprofile --no-ansi --config config.yaml run-schedule backup@profile
99,40 * * * *	user	cd /workdir && /home/resticprofile --no-ansi --config config.yaml run-schedule backup@profile
11,41 * * * *	user	different-script.sh
### end of resticprofile content, please leave this line intact ###
`

func TestStatusScheduleIntegrationUsingCrontab(t *testing.T) {
	crontab := filepath.Join(t.TempDir(), "crontab")
	err := os.WriteFile(crontab, []byte(scheduleIntegrationTestsCrontab), 0o600)
	require.NoError(t, err)

	testCases := []struct {
		description string
		configFile  string
		profileName string
		contains    string
		err         error
	}{
		{
			description: "all profiles",
			configFile:  "config.yaml",
			profileName: "",
			contains:    "Original form: *-*-* *:10,40:00",
		},
		{
			description: "display the profile present in crontab but deleted from configuration",
			configFile:  "config.yaml",
			profileName: "other-profile",
			contains:    "Original form: *-*-* *:10,40:00",
		},
		{
			description: "no profile from this config file",
			configFile:  "other-config.yaml",
			profileName: "profile",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.description, func(t *testing.T) {
			clog.SetTestLog(t)
			defer clog.CloseTestLog()

			cfg, err := config.Load(
				bytes.NewBufferString(fmt.Sprintf(scheduleIntegrationTestsConfiguration, crontab)),
				config.FormatYAML,
				config.WithConfigFile(tc.configFile),
			)
			require.NoError(t, err)
			require.NotNil(t, cfg)

			global, err := cfg.GetGlobalSection()
			require.NoError(t, err)
			require.NotNil(t, global)

			ctx := commandContext{
				Context: Context{
					config: cfg,
					global: global,
					request: Request{
						profile: tc.profileName,
					},
				},
			}
			output := &bytes.Buffer{}
			term.SetOutput(output)
			defer term.SetOutput(os.Stdout)

			err = statusSchedule(output, ctx)
			if tc.err != nil {
				assert.ErrorIs(t, err, tc.err)
				return
			}
			require.NoError(t, err)

			if len(tc.contains) > 0 {
				assert.Contains(t, output.String(), tc.contains)
				t.Log(output.String())
			} else {
				assert.Empty(t, output.String())
			}
		})
	}
}

func TestRemoveScheduleIntegrationUsingCrontab(t *testing.T) {
	crontab := filepath.Join(t.TempDir(), "crontab")
	err := os.WriteFile(crontab, []byte(scheduleIntegrationTestsCrontab), 0o600)
	require.NoError(t, err)
	clog.SetTestLog(t)
	defer clog.CloseTestLog()

	cfg, err := config.Load(
		bytes.NewBufferString(fmt.Sprintf(scheduleIntegrationTestsConfiguration, crontab)),
		config.FormatYAML,
		config.WithConfigFile("config.yaml"),
	)
	require.NoError(t, err)
	require.NotNil(t, cfg)

	global, err := cfg.GetGlobalSection()
	require.NoError(t, err)
	require.NotNil(t, global)

	ctx := commandContext{
		Context: Context{
			config: cfg,
			global: global,
			request: Request{
				profile: "profile",
			},
		},
	}
	output := &bytes.Buffer{}
	term.SetOutput(output)
	defer term.SetOutput(os.Stdout)

	// this should remove the 2 lines that resemble a resticprofile schedule
	err = removeSchedule(output, ctx)
	require.NoError(t, err)

	result, err := os.ReadFile(crontab)
	require.NoError(t, err)
	fmt.Println(string(result))

	// but one line in error should be left in the crontab
	err = statusSchedule(output, ctx)
	require.ErrorIs(t, err, crond.ErrEntryNoMatch)
}

func TestCreateScheduleIntegrationUsingCrontab(t *testing.T) {
	crontab := filepath.Join(t.TempDir(), "crontab")
	err := os.WriteFile(crontab, []byte(scheduleIntegrationTestsCrontab), 0o600)
	require.NoError(t, err)
	clog.SetTestLog(t)
	defer clog.CloseTestLog()

	cfg, err := config.Load(
		bytes.NewBufferString(fmt.Sprintf(scheduleIntegrationTestsConfiguration, crontab)),
		config.FormatYAML,
		config.WithConfigFile("config.yaml"),
	)
	require.NoError(t, err)
	require.NotNil(t, cfg)

	global, err := cfg.GetGlobalSection()
	require.NoError(t, err)
	require.NotNil(t, global)

	ctx := commandContext{
		Context: Context{
			config: cfg,
			global: global,
			request: Request{
				profile: "profile-schedule-struct",
			},
		},
	}
	output := &bytes.Buffer{}
	term.SetOutput(output)
	defer term.SetOutput(os.Stdout)

	// create one schedule
	err = createSchedule(output, ctx)
	require.NoError(t, err)

	result, err := os.ReadFile(crontab)
	require.NoError(t, err)
	assert.Contains(t, string(result), "20,50 * * * *")
	fmt.Println(string(result))

	output.Reset()
	err = statusSchedule(output, ctx)
	require.NoError(t, err)

	// verify the new schedule was added
	assert.Contains(t, output.String(), "Original form: *-*-* *:20,50:00")
	t.Log(output.String())
}

func TestCreateScheduleOverwriteExistingIntegrationUsingCrontab(t *testing.T) {
	const scheduleIntegrationTestsCrontab = `
### this content was generated by resticprofile, please leave this line intact ###
10,40 * * * *	user	cd /workdir && /home/resticprofile --no-ansi --config config.yaml run-schedule backup@profile-schedule-struct
11,41 * * * *	user	different-script.sh
### end of resticprofile content, please leave this line intact ###
`
	crontab := filepath.Join(t.TempDir(), "crontab")
	err := os.WriteFile(crontab, []byte(scheduleIntegrationTestsCrontab), 0o600)
	require.NoError(t, err)
	clog.SetTestLog(t)
	defer clog.CloseTestLog()

	cfg, err := config.Load(
		bytes.NewBufferString(fmt.Sprintf(scheduleIntegrationTestsConfiguration, crontab)),
		config.FormatYAML,
		config.WithConfigFile("config.yaml"),
	)
	require.NoError(t, err)
	require.NotNil(t, cfg)

	global, err := cfg.GetGlobalSection()
	require.NoError(t, err)
	require.NotNil(t, global)

	ctx := commandContext{
		Context: Context{
			config: cfg,
			global: global,
			request: Request{
				arguments: []string{"--all", "--reload", "--no-start"},
			},
		},
	}
	output := &bytes.Buffer{}
	term.SetOutput(output)
	defer term.SetOutput(os.Stdout)

	// create (or update) two schedules
	err = createSchedule(output, ctx)
	require.NoError(t, err)

	result, err := os.ReadFile(crontab)
	require.NoError(t, err)
	assert.Contains(t, string(result), "20,50 * * * *")
	assert.NotContains(t, string(result), "10,40 * * * *")
	fmt.Println(string(result))

	output.Reset()
	err = statusSchedule(output, ctx)
	require.NoError(t, err)

	// verify the schedule was replaced
	assert.Contains(t, output.String(), "Original form: *-*-* *:20,50:00")    // new one
	assert.NotContains(t, output.String(), "Original form: *-*-* *:10,40:00") // previous one
	t.Log(output.String())
}
